# -*- coding: utf-8 -*-
"""
    fanfou.py
    ==========
    
    3/6/2008

    class **Fanfou**
    -----------------
    
    Implement the function that Fanfou.com API provides. The latest version 
    is 0.0.1.
    This version will support:
        
        - get user time line [1]_ ::
        
            f = Fanfou()
            f.getUserTimeLine("jiyucn")
        
        - get friends time line ::
        
            f = Fanfou()
            f.getFriendsTimeLine("jiyucn")
        
        - get pulibc time line ::
        
            f = Fanfou()
            f.getPublicTimeLine()
        
        - get singal message ::
        
            f = Fanfou()
            f.getStatus("QAXTfesN0dE")
        
        - get user friends ::
        
            f = Fanfou()
            f.getFriends("jiyucn")
        
        - get user follows ::
        
            f = Fanfou()
            f.getFollows("jiyucn")
        
        - get user information ::
        
            f = Fanfou()
            f.getUser("jiyucn")
        
    This version will NOT support:
        
        - submit a fanfou message
        
        - delete a fanfou message
        
        - send a private message
        
        - delete a private message
        
        - show messages in private box
        
    .. [1] You can get the explaination of *TimeLine* from http://fanfou.com
"""

__version__ = '0.0.1'
__author__ = 'A.TNG <jiyucn@163.com>'
__url__ = 'http://blog.csdn.net/jiyucn'
__license__ = 'LGPL'
__docformat__ = 'restructuredtext'

__all__ = ["Fanfou", "Status", "User"]

import os
import sys
reload(sys)
sys.setdefaultencoding("GB2312")

import urllib
from xml.dom import minidom  
from xml.dom import DOMException

class Status:
    """This class represents the Status structure.
    
    status.created_at
    status.id
    status.text
    status.user
    """
    def __init__(self, created_at="", id="", text="", user=None):
        self.created_at = created_at
        self.id = id
        self.text = text
        self.user = user
        
    def __str__(self):
        return str("created_at:"+self.created_at+os.linesep+\
                     "id:"+self.id+os.linesep+\
                     "text:"+self.text+os.linesep+\
                     "user:"+str(self.user))
    
    def __eq__(self, y):
        # type not match
        if not isinstance(y, Status): 
            return False
        
        if ((self.created_at == y.created_at) and
            (self.id == y.id) and
            (self.text == y.text) and
            (self.user == y.user)):
            return True
        else:
            return False
    
    def __ne__(self, y):
        return not self.__eq__(y)

class User:
    """This class represents the User structure.
    
    user.screen_name
    user.id
    user.name
    user.location
    user.description
    user.profile_image_url
    user.url
    user.protected
    user.friends_count
    user.followers_count
    user.favourites_count
    user.statuses_count
    user.utc_offset
    user.status
    """
    def __init__(self, screen_name="", id="", name="", location="", 
                 description="", profile_image_url="", url="", protected="", 
                 friends_count=0, followers_count=0, favourites_count=0, 
                 statuses_count=0, utc_offset=0, status=None):
        self.screen_name = screen_name
        self.id = id
        self.name = name
        self.location = location
        self.description = description
        self.profile_image_url = profile_image_url
        self.url = url
        self.protected = protected
        self.friends_count = friends_count
        self.followers_count = followers_count
        self.favourites_count = favourites_count
        self.statuses_count = statuses_count
        self.utc_offset = utc_offset
        self.status = status
        
    def __str__(self):
        return str("screen_name:"+self.screen_name+os.linesep+\
                     "id:"+self.id+os.linesep+\
                     "name:"+self.name+os.linesep+\
                     "location:"+self.location+os.linesep+\
                     "description:"+self.description+os.linesep+\
                     "profile_image_url:"+self.profile_image_url+os.linesep+\
                     "url:"+self.url+os.linesep+\
                     "protected:"+self.protected+os.linesep+\
                     "friends_count:"+str(self.friends_count)+os.linesep+\
                     "followers_count:"+str(self.followers_count)+os.linesep+\
                     "favourites_count:"+str(self.favourites_count)+os.linesep+\
                     "statuses_count:"+str(self.statuses_count)+os.linesep+\
                     "utc_offset:"+str(self.utc_offset)+os.linesep+\
                     "status:"+str(self.status))
        
    def __eq__(self, y):
        # type not match
        if not isinstance(y, User): 
            return False
        
        if ((self.screen_name == y.screen_name) and
            (self.id == y.id) and
            (self.name == y.name) and
            (self.location == y.location) and
            (self.description == y.description) and
            (self.profile_image_url == y.profile_image_url) and
            (self.url == y.url) and
            (self.protected == y.protected) and
            (self.friends_count == y.friends_count) and
            (self.followers_count == y.followers_count) and
            (self.favourites_count == y.favourites_count) and
            (self.statuses_count == y.statuses_count) and
            (self.utc_offset == y.utc_offset) and
            (self.status == y.status)):
            return True
        else:
            return False

    def __ne__(self, y):
        return not self.__eq__(y)

class PrivateMessage:
    """"""
    def __init__(self, id="", text="", sender_id="", recipient_id="", 
                 created_at="", sender_screen_name="", 
                 recipient_screen_name=""):
        self.id = id
        self.text = text
        self.sender_id = sender_id
        self.recipient_id = recipient_id 
        self.created_at = created_at
        self.sender_screen_name = sender_screen_name
        self.recipient_screen_name = recipient_screen_name
        
    def __str__(self):
        return str({"id":self.id,
                     "text":self.text,
                     "sender_id":self.sender_id,
                     "recipient_id":self.recipient_id,
                     "created_at":self.created_at,
                     "sender_screen_name":self.sender_screen_name,
                     "recipient_screen_name":self.recipient_screen_name})
    
    def __eq__(self, y):
        # type not match
        if not isinstance(y, PrivateMessage): 
            return False
        
        if ((self.id == y.id) and
            (self.text == y.text) and
            (self.sender_id == y.sender_id) and
            (self.recipient_id == y.recipient_id) and
            (self.created_at == y.created_at) and
            (self.sender_screen_name == y.sender_screen_name) and
            (self.recipient_screen_name == y.recipient_screen_name)):
            return True
        else:
            return False
        
    def __ne__(self, y):
        return not self.__eq__(y)

class Fanfou:
    """wrap all API of fanfou.com into this Fanfou class."""
    def __init__(self):
        pass
    
    def getUrlPage(self, link):
        """
        get URL page from link -> string, page result of the link
        
        :Parameters:
            - `link`: url you want to retrieve.
        """
        try:
            ret = ""
            assert link <> "" 
            sock = urllib.urlopen(link)
            ret = sock.read()
            sock.close()
        except Exception, err:
            print >> sys.stderr, err

        return ret
    
    def parseTimeLine(self, data):
        """
        parse time line -> list, each item is a instance of Status
        
        :Parameters:
            - `data`: XML string, time line."""
        ret = []
        if not data: 
            return ret
        
        try:
            root = minidom.parseString(data).documentElement
            for snode in root.childNodes:
                if (snode.nodeType == snode.ELEMENT_NODE):
                    s = self.parseStatus(snode.toxml("UTF-8"))
                    if s:
                        ret.append(s)
        except DOMException, err:
            print >> sys.stderr, err
        return ret

        c = None
        try:
            chNode = minidom.parseString(data).getElementsByTagName("channel")[0]
            c = Channel(title = chNode.getElementsByTagName("title")[0].firstChild.data,
                        link = chNode.getElementsByTagName("link")[0].firstChild.data,
                        description = chNode.getElementsByTagName("description")[0].firstChild.data,
                        language = chNode.getElementsByTagName("language")[0].firstChild.data,
                        ttl = int(chNode.getElementsByTagName("ttl")[0].firstChild.data),
                        items = [])
            itemNodes = chNode.getElementsByTagName("item")
            for itemNode in itemNodes:
                it = Item(title = itemNode.getElementsByTagName("title")[0].firstChild.data,
                          description = itemNode.getElementsByTagName("description")[0].firstChild.data,
                          pubDate = itemNode.getElementsByTagName("pubDate")[0].firstChild.data,
                          guid = itemNode.getElementsByTagName("guid")[0].firstChild.data,
                          link =  itemNode.getElementsByTagName("link")[0].firstChild.data)
                c.items.append(it)

        except DOMException, err:
            print >> sys.stderr, err
            c = None
        return c
    
    def parseStatus(self, data):
        """
        parse status -> a Status instance, parsed from input XML string
        
        :Parameters:
            - `data`: XML string, time line."""
        s = None
        if not data: 
            return s

        try:
            root = minidom.parseString(data).documentElement
            created_at = u""
            id = u""
            text = u""
            user = None
            for node in root.childNodes:
                if (node.nodeType == node.ELEMENT_NODE) and (node.firstChild <> None):
                    if node.nodeName == "created_at":
                        created_at = node.firstChild.data
                    elif node.nodeName == "id":
                        id = node.firstChild.data
                    elif node.nodeName == "text":
                        text = node.firstChild.data
                    elif node.nodeName == "user":
                        user = self.parseUser(node.toxml("UTF-8"))
                    else:
                        pass
            s = Status(created_at, id, text, user)
        except DOMException, err:
            print >> stderr, err
            s = None
        return s
    
    def parseUser(self, data):
        """
        parse user info -> a User instance, parsed from input XML string
        
        :Parameters:
            - `data`: XML string, time line."""
        u = None
        if data == "": 
            return u
        try:
            root = minidom.parseString(data).documentElement
            screen_name = u""
            id = u""
            name = u""
            location = u""
            description = u""
            profile_image_url = u""
            url = u""
            protected = u""
            friends_count = 0
            followers_count = 0
            favourites_count = 0
            statuses_count = 0
            utc_offset = 0 
            status = None
            for n in root.childNodes:
                if (n.nodeType == n.ELEMENT_NODE) and (n.firstChild <> None):
                    if n.nodeName == "screen_name":
                        screen_name = n.firstChild.data
                    elif n.nodeName == "id":
                        id = n.firstChild.data
                    elif n.nodeName == "name":
                        name = n.firstChild.data
                    elif n.nodeName == "location":
                        location = n.firstChild.data
                    elif n.nodeName == "description":
                        description = n.firstChild.data
                    elif n.nodeName == "profile_image_url":
                        profile_image_url = n.firstChild.data
                    elif n.nodeName == "url":
                        url = n.firstChild.data
                    elif n.nodeName == "protected":
                        protected = n.firstChild.data
                    elif n.nodeName == "friends_count":
                        friends_count = int(n.firstChild.data)
                    elif n.nodeName == "followers_count":
                        followers_count = int(n.firstChild.data)
                    elif n.nodeName == "favourites_count":
                        favourites_count = int(n.firstChild.data)
                    elif n.nodeName == "statuses_count":
                        statuses_count = int(n.firstChild.data)
                    elif n.nodeName == "utc_offset":
                        utc_offset = int(n.firstChild.data)
                    elif n.nodeName == "status":
                        #status = Status(created_at=n.getElementsByTagName("created_at")[0].firstChild.data, 
                        #                id=n.getElementsByTagName("id")[0].firstChild.data, 
                        #                text=n.getElementsByTagName("text")[0].firstChild.data)
                        status = self.parseStatus(n.toxml("UTF-8"))
                    else:
                        pass
            u = User(screen_name, id, name, location, description, 
                     profile_image_url, url, protected, friends_count, 
                     followers_count, favourites_count, 
                     statuses_count, utc_offset, status)
        except DOMException, err:
            print >> sys.stderr, err
            u = None
        return u

    def getUserTimeLine(self, id, count=20):
        """
        display user message(s) -> list, each item a Status instance.
        
        :Parameters:
            - `id`: user id
            - `count`: range 1~20, default value is 20."""
        assert id <> ""
        url = "http://api.fanfou.com/statuses/user_timeline.rss?id=" + id \
            +"&count="+str(count)
        urlResult = self.getUrlPage(url)
        return self.parseTimeLine(urlResult)
        
    def getFriendsTimeLine(self, id, count=20):
        """
        display user and his/her friends message(s) -> list, 
        each item a Status instance.

        :Parameters:
            - `id`: user id
            - `count`: range 1~20, default vlalue is 20."""
        assert id <> ""
        url = "http://api.fanfou.com/statuses/friends_timeline.rss?id=" + id \
            + "&count="+str(count)
        urlResult = self.getUrlPage(url)
        return self.parseTimeLine(urlResult)
        
    def getPublicTimeLine(self, count=20):
        """
        show random message(s) -> list, each item a Status instance.
        
        :Parameters:
            - `count`: range 1~20, default vlalue is 20."""
        url = "http://api.fanfou.com/statuses/public_timeline.rss?count="+str(count)
        urlResult = self.getUrlPage(url)
        return self.parseTimeLine(urlResult)

    def getStatus(self, id):
        """
        show specific message -> a Status instance, parsed from input XML string.
        
        :Parameters:
            - `id`: message id."""
        assert id <> ""
        url = "http://api.fanfou.com/statuses/show.xml?id="+id
        urlResult = self.getUrlPage(url)
        return self.parseStatus(urlResult)
    
    def statusesUpdate(self, status, source=""):
        """
        submit a Fanfou message.
        THIS FEATURE WILL BE SUPPORTED LATTER
        
        :Parameters:
            - `status`: content of current Fanfou message
            - `source`: source of current Fanfou message. This parameter is optional. 
              This should be approved by Fanfou.com to match their database."""
        assert msg <> ""
        pass

    def statusesDestroy(self, id):
        """
        delete message
        THIS FEATURE WILL BE SUPPORTED LATTER
        
        :Parameters:
            - `id`: message id."""
        assert id <> ""
        pass
    
    def parseUsers(self, data):
        """
        parse users -> a list, each item is a User instance.
        
        :Parameters:
            - `data`: input XML string."""
        li = []
        if data == "": 
            return li
        try:
            root = minidom.parseString(data).documentElement
            for node in root.childNodes:
                if node.nodeType == node.ELEMENT_NODE and node.nodeName == "user":
                    screen_name = u""
                    id = u""
                    name = u""
                    location = u""
                    description = u""
                    profile_image_url = u""
                    url = u""
                    protected = u""
                    for n in node.childNodes:
                        if n.nodeType == n.ELEMENT_NODE and n.firstChild <> None:
                            if n.nodeName == "screen_name":
                                screen_name = n.firstChild.data
                            elif n.nodeName == "id":
                                id = n.firstChild.data
                            elif n.nodeName == "name":
                                name = n.firstChild.data
                            elif n.nodeName == "location":
                                location = n.firstChild.data
                            elif n.nodeName == "description":
                                description = n.firstChild.data
                            elif n.nodeName == "profile_image_url":
                                profile_image_url = n.firstChild.data
                            elif n.nodeName == "url":
                                url = n.firstChild.data
                            elif n.nodeName == "protected":
                                protected = n.firstChild.data
                            else:
                                pass
                    u = User(screen_name, id, name, location, description, 
                             profile_image_url, url, protected)
                    li.append(u)
        except DOMException, err:
            print >> sys.stderr, err

        return li
    
    def getFriends(self, id):
        """
        show friends of current user -> a list, each item is User instance.
        
        :Parameters:
            - `id`: user id."""
        url = "http://api.fanfou.com/users/friends.xml?id="+id
        urlResult = self.getUrlPage(url)
        return self.parseUsers(urlResult)
    
    def getFollows(self, id):
        """
        show follows of current user -> a list, each item is User instance.
        
        :Parameters:
            - `id`: user id."""
        assert id <> ""
        url = "http://api.fanfou.com/users/followers.xml?id="+id
        urlResult = self.getUrlPage(url)
        return self.parseUsers(urlResult)

    def getUser(self, id):
        """
        show user info -> a User instance, parsed from input XML string.
        
        :Parameters:
            - `id`: user id."""
        assert id <> ""
        url = "http://api.fanfou.com/users/show.xml?id="+id
        urlResult = self.getUrlPage(url)
        return self.parseUser(urlResult)
    
    def friendshipsCreate(self, id):
        """
        add a new friend.
        THIS FEATURE WILL BE SUPPORTED LATTER
        
        :Parameters:
            - `id`: user id."""
        assert id <> ""
        pass
    
    def friendshipsDestroy(self, id):
        """
        delete a friend.
        THIS FEATURE WILL BE SUPPORTED LATTER
        
        :Parameters:
            - `id`: user id."""
        assert id <> ""
        pass
    
    def privateMessagesInbox(self, count=20):
        """
        display user private message.
        THIS FEATURE WILL BE SUPPORTED LATTER
        
        :Parameters:
            - `count`: range 1~20, default value is 20."""
        return None
    
    def privateMessagesNew(self, user, text):
        """
        send a private message.
        THIS FEATURE WILL BE SUPPORTED LATTER
        
        :Parameters:
            - `user`: the receipent."""
        pass
    
    def privateMessagesDestroy(self, id):
        """
        delete a private message.
        THIS FEATURE WILL BE SUPPORTED LATTER
        
        :Parameters:
            - `id`: private message id."""
        assert id <> ""
        pass
    
    def one(self):
        """a simple function to test testFanfou.py"""
        return True

if __name__ == "__main__":
    pass
